// Copyright (c) 2025 Shenzhen Kaihong Digital Industry Development Co., Ltd.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use std::path::Path;

use anyhow::Context;
use serde::{de::Visitor, Deserialize, Serialize};
use sha2::{Digest, Sha256};

#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct DeployConfig {
    #[serde(default, skip_serializing_if = "NoDeployCrossCompileIsolation::is_none")]
    pub no_cross_compile_isolation: NoDeployCrossCompileIsolation,
    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
    pub as_host: bool,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub script: Option<String>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct DeployConfigOverrides {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub no_cross_compile_isolation: Option<NoDeployCrossCompileIsolation>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub as_host: Option<bool>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub script: Option<String>,
}

#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub enum NoDeployCrossCompileIsolation {
    #[default]
    None,
    All,
    Paths(Vec<String>),
}

impl Serialize for NoDeployCrossCompileIsolation {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer
    {
        match self {
            NoDeployCrossCompileIsolation::None => serializer.serialize_bool(false),
            NoDeployCrossCompileIsolation::All => serializer.serialize_bool(true),
            NoDeployCrossCompileIsolation::Paths(vec) => Vec::serialize(vec, serializer),
        }
    }
}

impl<'de> Deserialize<'de> for NoDeployCrossCompileIsolation {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::de::Deserializer<'de>
    {
        struct NoDeployCrossCompileIsolationVisitor;

        impl<'de> Visitor<'de> for NoDeployCrossCompileIsolationVisitor {
            type Value = NoDeployCrossCompileIsolation;
        
            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
                formatter.write_str("bool or string list")
            }

            fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E>
            where
                E: serde::de::Error
            {
                match v {
                    true => Ok(NoDeployCrossCompileIsolation::All),
                    false => Ok(NoDeployCrossCompileIsolation::None),
                }
            }

            fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
            where
                A: serde::de::SeqAccess<'de>
            {
                let mut paths = if let Some(len) = seq.size_hint() {
                    Vec::with_capacity(len)
                } else {
                    Vec::new()
                };
                while let Some(path) = seq.next_element::<String>()? {
                    paths.push(path);
                }
                Ok(NoDeployCrossCompileIsolation::Paths(paths))
            }
        }

        deserializer.deserialize_any(NoDeployCrossCompileIsolationVisitor)
    }
}

impl DeployConfig {
    pub fn merge_overrides(&mut self, overrides: &DeployConfigOverrides) {
        if let Some(no_deploy_cross_compile_isolation) = overrides.no_cross_compile_isolation.as_ref() {
            self.no_cross_compile_isolation = no_deploy_cross_compile_isolation.clone();
        }
        if let Some(as_host) = overrides.as_host {
            self.as_host = as_host;
        }
        if let Some(patches) = overrides.script.as_ref() {
            self.script = Some(patches.clone());
        }
    }

    pub async fn spec_hash<P: AsRef<Path>>(&self, hasher: &mut Sha256, base_dir: P) -> anyhow::Result<()> {
        self.no_cross_compile_isolation.spec_hash(hasher);
        hasher.update([self.as_host as u8]);
        if let Some(script) = &self.script {
            let path = base_dir.as_ref().join(script);
            let content = tokio::fs::read_to_string(&path).await
                .context(format!("failed to read deploy script: {}", path.display()))?;
            hasher.update(content.as_bytes());
        }
        Ok(())
    }
}

impl NoDeployCrossCompileIsolation {
    pub fn is_none(&self) -> bool {
        *self == Self::None
    }

    fn spec_hash(&self, hasher: &mut Sha256) {
        match self {
            NoDeployCrossCompileIsolation::None => {
                hasher.update([0]);
            },
            NoDeployCrossCompileIsolation::All => {
                hasher.update([1]);
            },
            NoDeployCrossCompileIsolation::Paths(paths) => {
                hasher.update([2]);
                for path in paths {
                    hasher.update(path.as_bytes());
                }
            },
        }
    }
}

#[cfg(test)]
mod test {
    use serde::Deserialize;

    use crate::config::pkg_config::NoDeployCrossCompileIsolation;

    #[test]
    fn test_no_deploy_cross_compile_isolation_serialize() {
        let mut value = String::new();
        serde::Serialize::serialize(
            &NoDeployCrossCompileIsolation::All,
            toml::ser::ValueSerializer::new(&mut value)
        ).unwrap();
        assert_eq!(value, "true".to_string());

        value.clear();
        serde::Serialize::serialize(
            &NoDeployCrossCompileIsolation::None,
            toml::ser::ValueSerializer::new(&mut value)
        ).unwrap();
        assert_eq!(value, "false".to_string());

        value.clear();
        serde::Serialize::serialize(
            &NoDeployCrossCompileIsolation::Paths(vec!["path".to_string()]),
            toml::ser::ValueSerializer::new(&mut value)
        ).unwrap();
        assert_eq!(value, "[\"path\"]".to_string());
    }

    #[test]
    fn test_no_deploy_cross_compile_isolation_deserialize() {
        assert_eq!(
            NoDeployCrossCompileIsolation::deserialize(toml::de::ValueDeserializer::new(
                "true"
            )),
            Ok(NoDeployCrossCompileIsolation::All)
        );
        assert_eq!(
            NoDeployCrossCompileIsolation::deserialize(toml::de::ValueDeserializer::new(
                "false"
            )),
            Ok(NoDeployCrossCompileIsolation::None)
        );
        assert_eq!(
            NoDeployCrossCompileIsolation::deserialize(toml::de::ValueDeserializer::new(
                "[\"path\"]"
            )),
            Ok(NoDeployCrossCompileIsolation::Paths(vec!["path".to_string()]))
        );
    }
}